# Copyright (c) 2012 Huseyin BIYIK.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the GNU Public License v3.0
# which accompanies this distribution, and is available at
# http://www.gnu.org/licenses/gpl.html
# 
# Contributors:
#     Huseyin BIYIK - initial API and implementation
#-------------------------------------------------------------------------------


#TODO LIST:

# 1. add call stream synchronizer
# 2. add multi stream buffering mechanism
 
# ISSUES:

# 1. buffering is impossimble when  I/O buffer is full udp socket does not send proper packets, Python specific GIL issue
# Need to implement flow control on datagrams

from defs import *
import threading, Queue, time
stypes = []
typeparams = ["name","enabled","payload","out_func","timestamp"] 
##to be filled in runtime

class stream():
	def __init__(self, callid):
		self.callid = callid


	def set_type(self, name, param, value):
		if name in stypes and param in typeparams:
			if not hasattr(self,name):
				setattr(self, name, self.type(name))
			setattr(getattr(self,name),param,value)
		else:
			logger.error(" %s stream type is not defined with %s", name,param)
	
	def get_type_param(self, attr,param):
		if hasattr(self,attr):
			return getattr(getattr(self, attr),param)
		else:
			return 0
		
	def get_types(self, enabled=True):
		type = []
		for stype in stypes:
			if hasattr(self, stype):
				if enabled and self.get_type_param(stype, "enabled"):
					type.append(getattr(self, stype))
				elif not enabled:
					type.append(getattr(self, stype))
		return type


	class type():
		def __init__(self, name, enabled=False):
			self.name = name
			self.enabled = enabled
		
	def close(self):
		logger.info("Closing stream")
		self.buffer.close()

class buffer():
	def __init__(self, stream):
		self.q = Queue.PriorityQueue()
		self.is_closing = False
		self.stream = stream
		
	def add(self, type, payload, timestamp):
		if self.stream.get_type_param(type,"enabled"):
			self.q.put((timestamp, payload))
			#print self.netspeed/1000 
			logger.debug("Buffer received %d byte %s type data ",len(payload),type)
			bufsize=self.q.qsize()
			if bufsize < 10:
				logger.info("Buffering..")
				return
			swap=""
			prebuf=""
			for i in range(2):
				try:
					timestamp, payload = self.q.get(True,1)
					#print timestamp
				except Queue.Empty:
					return
				##implement multi type buffering and sync things. Later it is fuckin confusing
				if self.stream.get_type_param(type,"enabled"):
					out_func=self.stream.get_type_param(type, "out_func")
					prebuf+=swap+payload
					if len(prebuf)%2 != 0:
						swap=prebuf[-1:]
						prebuf=prebuf[:-1]
					else:
						swap=""	
					out_func(prebuf)
					#print self.q.qsize()
					prebuf=""
					#logger.debug("Buffer wrote %s ms %s type data to device with %d speed",str(deltat),type,self.devspeed)
					#print self.devspeed/1000
			return True
		else:
			logger.error("Stream either does not have or did not enable %s type", type)
			return False		
	
	def close(self):
		logger.info("Closing buffer thread")
		del self
		return True

class reader(threading.Thread):
	def __init__(self, sender, cm, s_type, s_func):
		threading.Thread.__init__(self)
		self.sender = sender
		self.cm = cm
		self.s_func = s_func
		self.s_type = s_type
		self.is_closing = False
		self.start()
	
	def run(self):
		while 1:
			if self.is_closing:
				return True
			chunk=int(getattr(self.cm,self.s_type+"_chunk"))
			if chunk > 0 :
				payload, timestamp = self.s_func(chunk, udp_packet_size - max_header)
			else:
				continue
			ts=time.time() 
			for cllid in self.cm.calls:
				if self.cm.calls[cllid].stream.get_type_param(self.s_type,"enabled"):
					self.cm.calls[cllid].stream.set_type(self.s_type,"payload",payload)
					self.cm.calls[cllid].stream.set_type(self.s_type,"timestamp",timestamp) 
					threading.Thread(target=self.sender,args=(4, self.cm.calls[cllid])).start()
					logger.debug("wrote %d byte %s for call '%s'",len(payload),self.s_type,cllid)
			tdiff=int((time.time()-ts)*1000)
#			ai.out_func(payload)
			setattr(self.cm,self.s_type+"_chunk",chunk+(tdiff+100-chunk)/2)
			ptime=int((time.time()-ts)*1000)
#			newchunk=chunk+(ptime-chunk)/2
#			setattr(self.cm,stype+"_chunk",newchunk)
#			logger.info("% read chunk is now %d for call '%s'",stype,newchunk,cll.id)

	def close(self):
		setattr(self.cm, self.s_type + "_chunk", 0)
		self.is_closing = True
		logger.info("Closing %s stream reader", self.s_type)
		threading.Thread.join(self)
		return True
